﻿using FoundationLib.AddIns.FileNameSynchroniser.Resources;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FoundationLib.AddIns.FileNameSynchroniser
{
	/// <summary>
	/// Represents the root of the file name changer EA add-in
	/// </summary>
	public class Root
	{
		private readonly string[] validTypes = new string[] { "Class", "Interface", "Enumeration", "Primitive" };
		private const string MENU_NAME = "&Set File Paths";
		private const string ROOT_PATH_TAG = "rootPath";
		private Dictionary<int, string> paths;

		/// <summary>
		/// Creates a new add-in root
		/// </summary>
		public Root()
		{
			paths = new Dictionary<int, string>();
		}

		/// <summary>
		/// Called by Enterprise Architect upon connect
		/// </summary>
		/// <param name="repository">The repository</param>
		/// <returns>An empty string</returns>
		public string EA_Connect(EA.Repository repository)
		{
			return String.Empty;
		}

		/// <summary>
		/// Called by Enterprise Architect upon disconnect
		/// </summary>
		public void EA_Disconnect()
		{
			GC.Collect();
			GC.WaitForPendingFinalizers();
		}

		/// <summary>
		/// Returns a list of menu items applicable to this add-in
		/// </summary>
		/// <returns>A string representing the menu item to display</returns>
		/// <param name="repository">The repository</param>
		/// <param name="menuLocation">The location where the menu is displayed</param>
		/// <param name="menuName">The name of the menu in case a sub-menu is called</param>
		public string EA_GetMenuItems(EA.Repository repository, string menuLocation, string menuName)
		{
			object element = null;
			EA.ObjectType objType = repository.GetContextItem(out element);
			if (objType == EA.ObjectType.otPackage)
			{
				return MENU_NAME;
			}
			else if (objType == EA.ObjectType.otElement)
			{
				EA.Element item = element as EA.Element;
				if (item != null)
				{
					if (validTypes.Contains(item.Type))
						return MENU_NAME;
				}
			}
			return null;
		}

		/// <summary>
		/// Called when a menu item is clicked, initiates the action associated with the
		/// clicked menu item
		/// </summary>
		/// <param name="repository">The repository</param>
		/// <param name="menuLocation">The location where the menu is called</param>
		/// <param name="menuName">The menu name</param>
		/// <param name="itemName">The name of the item clicked in the menu</param>
		public void EA_MenuClick(EA.Repository repository, string menuLocation, string menuName, string itemName)
		{
			if (itemName == MENU_NAME)
			{
				object obj = null;
				EA.ObjectType objType = repository.GetContextItem(out obj);
				EA.Element element = null;
				if (objType == EA.ObjectType.otPackage)
				{
					EA.Package package = obj as EA.Package;
					if (package != null)
						element = package.Element;
				}
				else if (objType == EA.ObjectType.otElement)
				{
					element = obj as EA.Element;
				}
				if (element != null)
				{
					if (element.Type == "Package")
					{
						EA.Package package = repository.GetPackageByGuid(element.ElementGUID);
						this.paths.Clear();
						this.ChangeAllElementPaths(repository, package);
					}
					else if (validTypes.Contains(element.Type))
					{
						this.paths.Clear();
						this.ChangeElementPath(repository, element);
					}
				}
			}
		}

		/// <summary>
		/// Retrieves the element file extension based on its target language
		/// </summary>
		/// <param name="element">The element whose extension to retrieve</param>
		/// <returns>The extension like so: ".cs"</returns>
		private string CreateElementFileExtension(EA.Element element)
		{
			string language = element.Gentype;
			if (String.IsNullOrWhiteSpace(language))
				return Extensions.CSharp;
			language = language.Replace("#", "Sharp");
			string extension = Extensions.ResourceManager.GetString(language);
			if (String.IsNullOrWhiteSpace(extension))
				extension = Extensions.CSharp;
			return extension;
		}

		/// <summary>
		/// Returns the package root path, which is either the tagged value attached to the
		/// given package or its filename property. If none of these values is present,
		/// NULL is returned.
		/// </summary>
		/// <returns>The root path or NULL if none is defined</returns>
		/// <param name="package"></param>
		private string GetPackageRootPath(EA.Package package)
		{
			string packageFile = null;

			EA.TaggedValue tag = package.Element.TaggedValues.GetByName(ROOT_PATH_TAG);
			if (tag != null && !String.IsNullOrWhiteSpace(tag.Value))
				packageFile = tag.Value;
			else if (!String.IsNullOrWhiteSpace(package.Element.Genfile))
				packageFile = package.Element.Genfile;
		
			return packageFile;
		}

		/// <summary>
		/// Returns the package name or the package code generation file name if it is set
		/// </summary>
		/// <param name="package">The package</param>
		/// <returns>The path to use for the package, which is either its code generation file or its name</returns>
		private string GetPackagePath(EA.Package package)
		{
			string packageFile = this.GetPackageRootPath(package);

			string packageName = package.Name;
			string value = !String.IsNullOrWhiteSpace(packageFile) ? packageFile : packageName;
			return value;
		}

		/// <summary>
		/// Retrieves the absolute file path of an element based on its position in the package tree. 
		/// This method traverses up the package tree until it finds a package is marked as a namespace root or 
		/// until the model root is reached.
		/// </summary>
		/// <param name="repository">The EA repository</param>
		/// <param name="element">The element whose path to retrieve</param>
		/// <returns>The absolute path</returns>
		private string CreateElementPath(EA.Repository repository, EA.Element element)
		{
			string path = element.Name + this.CreateElementFileExtension(element);
			EA.Package package = repository.GetPackageByID(element.PackageID);

			string packagePath = null;
			if (this.paths.TryGetValue(package.PackageID, out packagePath))
			{
				return Path.Combine(packagePath, path);
			}

			if (package.IsNamespace || !String.IsNullOrWhiteSpace(this.GetPackageRootPath(package)))
			{
				packagePath = this.GetPackagePath(package);
				this.paths.Add(package.PackageID, packagePath);
				return Path.Combine(packagePath, path);
			}

			packagePath = String.Empty;
			EA.Package current = package;
			while (!current.IsNamespace && current.ParentID != 0 && 
				String.IsNullOrWhiteSpace(this.GetPackageRootPath(current)))
			{
				packagePath = Path.Combine(this.GetPackagePath(current), packagePath);
				current = repository.GetPackageByID(current.ParentID);
			}
			packagePath = Path.Combine(this.GetPackagePath(current), packagePath);
			this.paths.Add(package.PackageID, packagePath);

			return Path.Combine(packagePath, path);
		}

		/// <summary>
		/// Changes the filename of the element
		/// </summary>
		/// <param name="repository">The EA repository</param>
		/// <param name="element">The element</param>
		private void ChangeElementPath(EA.Repository repository, EA.Element element)
		{
			if (String.IsNullOrWhiteSpace(element.Name))
				return;

			string path = this.CreateElementPath(repository, element);
			element.Genfile = path;
			element.Update();
		}

		/// <summary>
		/// Recursively changes the filenames of all files in the given package
		/// </summary>
		/// <param name="repository">The EA repository</param>
		/// <param name="package">The package</param>
		private void ChangeAllElementPaths(EA.Repository repository, EA.Package package)
		{
			foreach (EA.Package child in package.Packages)
			{
				this.ChangeAllElementPaths(repository, child);
			}

			foreach (EA.Element element in package.Elements)
			{
				if (!validTypes.Contains(element.Type))
					continue;

				string path = this.CreateElementPath(repository, element);
				element.Genfile = path;
				element.Update();
			}
		}
	}
}
